package com.gitee.hellowllh.dlearn.algorithm.leetcode.skill.pointer;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import static org.junit.Assert.assertEquals;

/**
 * <a href='https://leetcode-cn.com/problems/linked-list-cycle/'>建议看连接的题目内容。。有图片.解释</a>
 *  给定一个链表，判断链表中是否有环。 <br/>
 *
 *  给定一个链表，判断链表中是否有环。
 *
 * 如果链表中有某个节点，可以通过连续跟踪 next 指针再次到达，则链表中存在环。 为了表示给定链表中的环，我们使用整数 pos 来表示链表尾连接到链表中的位置（索引从 0 开始）。 如果 pos 是 -1，则在该链表中没有环。注意：pos 不作为参数进行传递，仅仅是为了标识链表的实际情况。
 *
 * 如果链表中存在环，则返回 true 。 否则，返回 false 。
 *
 *  
 *
 * 进阶：
 *
 * 你能用 O(1)（即，常量）内存解决此问题吗？
 *
 * 来源：力扣（LeetCode）
 * 链接：https://leetcode-cn.com/problems/linked-list-cycle
 * 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
 *
 */
@RunWith(Parameterized.class)
public class LinkedListCycle {

    ListNode head;
    boolean out;

    public LinkedListCycle(ListNode head, boolean out) {
        this.head = head;
        this.out = out;
    }
    @Parameterized.Parameters
    public static Collection prepareData() {
        // one
        ListNode one = new ListNode(3);
        ListNode one1 = new ListNode(2);
        one.next = one1;
        ListNode one2 = new ListNode(0);
        one1.next = one2;
        ListNode one3 = new ListNode(4);
        one2.next = one3;
        one3.next = one1; // 环
        // two
        ListNode two = new ListNode(1);
        ListNode two1 = new ListNode(2);
        two.next = two1;
        two1.next = two; // 环
        // 示例:
        Object[][] object = {
                {  one, true  } ,
                {  two, true  } ,
                {  new ListNode(1), false  } ,
        };
        return Arrays.asList(object);// 数组转化成集合形式。
    }


    @Test
    public void testOk() {
        // 注意是调用的成员变量。
        boolean i = this.hasCycle(this.head);
        assertEquals(this.out,i);
    }
    // 利用set集合实现..   时间复杂度为o(n),空间为o(n)
    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while (head != null){
            if (!set.add(head)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }

    @Test
    public void testOk2() {
        // 注意是调用的成员变量。
        boolean i = this.hasCycle2(this.head);
        assertEquals(this.out,i);
    }
    // 你能用 O(1)（即，常量）内存解决此问题吗？
    public boolean hasCycle2(ListNode head) {
        // null
        if(head ==null) return false;

        ListNode slow = head;
        ListNode fast = head.next;

        // 快的不存在,就说明已经到底了。 直接flase
        while(  fast != null ){
            if( slow == fast ){
                return true;
            }else{
                slow = slow.next;
                // fast null
                fast = fast.next;
                if(fast == null) return false;
                fast = fast.next;
            }
        }
        return false;
    }


    static class ListNode {
        int val;
        ListNode next;
        public ListNode(int x) {val = x;
            next = null;
        }
        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
    }

    /**
     * 方法一：哈希表
     * 思路及算法
     *
     * 最容易想到的方法是遍历所有节点，每次遍历到一个节点时，判断该节点此前是否被访问过。
     *
     * 具体地，我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点，如果该节点已经存在于哈希表中，则说明该链表是环形链表，否则就将该节点加入哈希表中。重复这一过程，直到我们遍历完整个链表即可。
     *
     * 代码
     *
     * JavaC++Python3GolangC
     *
     * public class Solution {
     *     public boolean hasCycle(ListNode head) {
     *         Set<ListNode> seen = new HashSet<ListNode>();
     *         while (head != null) {
     *             if (!seen.add(head)) {
     *                 return true;
     *             }
     *             head = head.next;
     *         }
     *         return false;
     *     }
     * }
     * 复杂度分析
     *
     * 时间复杂度：O(N)O(N)，其中 NN 是链表中的节点数。最坏情况下我们需要遍历每个节点一次。
     *
     * 空间复杂度：O(N)O(N)，其中 NN 是链表中的节点数。主要为哈希表的开销，最坏情况下我们需要将每个节点插入到哈希表中一次。
     *
     * 方法二：快慢指针
     * 思路及算法
     *
     * 本方法需要读者对「Floyd 判圈算法」（又称龟兔赛跑算法）有所了解。
     *
     * 假想「乌龟」和「兔子」在链表上移动，「兔子」跑得快，「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时，如果该链表中没有环，那么「兔子」将一直处于「乌龟」的前方；如果该链表中有环，那么「兔子」会先于「乌龟」进入环，并且一直在环内移动。等到「乌龟」进入环时，由于「兔子」的速度快，它一定会在某个时刻与乌龟相遇，即套了「乌龟」若干圈。
     *
     * 我们可以根据上述思路来解决本题。具体地，我们定义两个指针，一快一满。慢指针每次只移动一步，而快指针每次移动两步。初始时，慢指针在位置 head，而快指针在位置 head.next。这样一来，如果在移动的过程中，快指针反过来追上慢指针，就说明该链表为环形链表。否则快指针将到达链表尾部，该链表不为环形链表。
     *
     *
     * 1 / 5
     *
     * 细节
     *
     * 为什么我们要规定初始时慢指针在位置 head，快指针在位置 head.next，而不是两个指针都在位置 head（即与「乌龟」和「兔子」中的叙述相同）？
     *
     * 观察下面的代码，我们使用的是 while 循环，循环条件先于循环体。由于循环条件一定是判断快慢指针是否重合，如果我们将两个指针初始都置于 head，那么 while 循环就不会执行。因此，我们可以假想一个在 head 之前的虚拟节点，慢指针从虚拟节点移动一步到达 head，快指针从虚拟节点移动两步到达 head.next，这样我们就可以使用 while 循环了。
     *
     * 当然，我们也可以使用 do-while 循环。此时，我们就可以把快慢指针的初始值都置为 head。
     *
     * 代码
     *
     * JavaC++Python3GolangC
     *
     * public class Solution {
     *     public boolean hasCycle(ListNode head) {
     *         if (head == null || head.next == null) {
     *             return false;
     *         }
     *         ListNode slow = head;
     *         ListNode fast = head.next;
     *         while (slow != fast) {
     *             if (fast == null || fast.next == null) {
     *                 return false;
     *             }
     *             slow = slow.next;
     *             fast = fast.next.next;
     *         }
     *         return true;
     *     }
     * }
     * 复杂度分析
     *
     * 时间复杂度：O(N)O(N)，其中 NN 是链表中的节点数。
     *
     * 当链表中不存在环时，快指针将先于慢指针到达链表尾部，链表中每个节点至多被访问两次。
     *
     * 当链表中存在环时，每一轮移动后，快慢指针的距离将减小一。而初始距离为环的长度，因此至多移动 NN 轮。
     *
     * 空间复杂度：O(1)O(1)。我们只使用了两个指针的额外空间。
     *
     * 作者：LeetCode-Solution
     * 链接：https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode-solution/
     * 来源：力扣（LeetCode）
     * 著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
     *
     */


    /**
     * 总结:
     * 深入理解快慢指针算法 -- 链表环路检测 : https://zhuanlan.zhihu.com/p/361049436  可以解决快慢指针的疑惑..
     */

}
